gitee 用法：
    git push -u origin master 上传仓库
    gitee add (文件) 添加文件到暂存区


程序员知识结构
    1、 计算机语言
        c、 c++

    2、 应用开发（高编）
        数据存储：文件和数据库
        多任务编程：进程和线程 同步和互斥
        网络编程：udp  tpc

    3、 c++  QT开发智能设备界面
    4、单片机 固件库

sizeof和strlen的区别：
    sizeof是C语言中的一个单目运算符，用来计算数据类型所占空间的大小，单位为字节；而strlen是一个函数，用来计算字符串长度。


应用开发概念

一、重要概念
    1、操作系统五大管理功能
        （1）文件管理
        （2）网络管理
        （3）内存管理
        （4）多任务管理
        （5）设备管理
    2、系统调用
        由Linux操作系统提供一套API或者（一套函数）
        应用程序通过调用“系统调用”和操作系统沟通（获取操作系统提供服务）
        系统调用以函数的形式提供，共计提供100多个系统调用

        学习调用库函数：
            功能 各个参数意义  返回值意义

二、文件编程
     1、文件概念
     2、文件编程
         对文件实现读写操作
    3、编程步骤：
        打开或创建文件---》读写文件--》关闭文件
    4、文件编程API
        int open(const char *pathname, int flags);
            flags:O_RDONLY
                O_WRONLY
                O_RDWR--读写

        ssize_t read(int fd, void *buf, size_t count);
        ssize_t write(int fd, const void *buf, size_t count);
        off_t lseek(int fd, off_t offset, int whence);

                    int close(int fd);

        malloc（）   free（）



文件变成步骤
    1、打开文件



    文件权限
           -w- r-- r--
    目录权限：
              r--查看目录内容
              w--能在目录中新建 删除 重命名一个对象
              x--能进入该目录 


    2、读写
        ssize_t read(int fd, void *buf, size_t count);
        从文件fd读取count字节存入以buf为起始地址的空间中
        返回值：
            （1）返回读取字节数
                count或者小于count
            （2）返回值是0
                读到文件末尾
            (3)返回值是-1，错误
        ssize_t write(int fd, const void *buf, size_t count);
        从buf为起始地址空间中的count个字数据保存到fd表示文件中
        返回值：
            （1）正确写入的字节数count
            （2）返回值是-1，写入错误

    注意：文件读写位置pos
            读写函数从pos表示位置往后读写count字节，自动改变pos的值
            通过lseek函数修改poa之后再读写

        off_t lseek(int fd, off_t offset, int whence);

    3、关闭文件
        close(fd);

获取文件详细信息：
    int stat(count char*pathname,struct stat *statbuf)

    功能：获取文件详细信息

    struct stat st;
    memset(&st,0,sizeof(struct stat));
    stat("./info.txt",&st);

    st.st_size
  
   struct stat
{
    dev_t st_dev; //device 文件的设备编号
    ino_t st_ino; //inode 文件的节点
    mode_t st_mode; //protection 文件的类型和存取的权限
    nlink_t st_nlink; //number of hard links 连到该文件的硬连接数目, 刚建立的文件值为1.
    uid_t st_uid; //user ID of owner 文件所有者的用户ID
    gid_t st_gid; //group ID of owner 文件所有者的组ID
    dev_t st_rdev; //device type 若此文件为设备文件, 则为其设备编号
    off_t st_size; //total size, in bytes 文件大小, 以字节计算
    unsigned long st_blksize; //blocksize for filesystem I/O 文件系统的I/O 缓冲区大小.
    unsigned long st_blocks; //number of blocks allocated 占用文件区块的个数, 每一区块大小为512 个字节.
    time_t st_atime; //time of lastaccess 文件最近一次被存取或被执行的时间, 一般只有在用mknod、utime、read、write 与tructate 时改变.
    time_t st_mtime; //time of last modification 文件最后一次被修改的时间, 一般只有在用mknod、utime 和write 时才会改变
    time_t st_ctime; //time of last change i-node 最近一次被更改的时间, 此参数会在文件所有者、组、权限被更改时更新
};

int access(const char *pathname, int mode);
  功能：判断对文件的操作权限         
    R_OK 是否有读权限
    W_OK 是否有写权限
    X_OK 是否有执行权限
    F_OK 测试一个文件是否存在


目录编程：
    1、操作
        打开、读取、关闭、新建、删除、改名
    2、操作目录系统调用
        重新命名
        int rename(const char *oldpath, const char *newpath);
        删除文件
        int remove(const char *pathname);
        创建目录
        int mkdir(const char *pathname, mode_t mode);
        删除空目录
        int rmdir(const char *dirname);
        
    读取目录内容
        打开-》读取-》关闭
        DIR *opendir(const char *name);

        struct dirent *readdir(DIR *dir);返回结构体指针，指向目录下一个文件信息

        int closedir(DIR *dir);

        struct dirent
            {
                long d_ino;  /*inode number 索引节点号*/
                off_t d_off; /*offset to this dirent 在目录文件中的偏移*/
                unsigned short d_reclen; /*length of this d_name 文件名长*/
                unsigned char d_type;    /*the type of d_name 文件类型*/
                char d_name [NAME_MAX+1]; /*文件名，最长255字符，NAME_MAX 在limits.h中定义*/
            };

        注意：readdir返回的结构体指针，指向的是一个堆内存，需要用free释放


int (const char *str)
    const修饰符进行修饰，这样防止不必要的人为修改，使用的方法与局部变量是相同的。


注意：break和continue的区别
1、break用于跳出一个循环体或者完全结束一个循环，不仅可以结束其所在的循环，还可结束其外层循环。

注意：
（1）只能在循环体内和switch语句体内使用break。
（2）不管是哪种循环，一旦在循环体中遇到break，系统将完全结束循环，开始执行循环之后的代码。
（3）当break出现在循环体中的switch语句体内时，起作用只是跳出该switch语句体，并不能终止循环体的执行。若想强行终止循环体的执行，可以在循环体中，但并不在switch语句中设置break语句，满足某种条件则跳出本层循环体。

2、continue语句的作用是跳过本次循环体中剩下尚未执行的语句，立即进行下一次的循环条件判定，可以理解为只是中止(跳过)本次循环，接着开始下一次循环。

注意：
（1）continue语句并没有使整个循环终止。
（2）continue 只能在循环语句中使用，即只能在 for、while 和 do…while 语句中使用。


1、 main 函数传参数
原型：int main(int argc, char *argv[])
        {
            return 0;
        }
    （1）给main函数传递仅能传递字符串，传递一个或多个字符串，不能传递整型、浮点型、结构体等数据类型。
    （2）通过命令运行程序方式传递数据
    （3）argc：存放是命令输入字符串
    （4）ardv：存放指针数组名
            argv[0]->可执行程序名字
            argv[1]->传递第一个字符串
            argv[2]->传递第二个字符串
2、作用
        实现liunx命令源代码
        实现一个cat命令


第三方库：
    1、库分类：
        标准库、第三方库
    2、标准库：
      C语言开发机构开发一套库函数
      具有公共功能
      系统提供的库，一般位于/usr/include目录下，如stdio.h、stdlib.h、string.h等。
    3、第三方库
        针对有一个方面应该，由机构、组织或者企业提供一套函数
库提供形式：
    （1）源码库：提供.c和.h文件
    （2）二进制库：提供.a或.so和.h文件
4、程序开发流程
    编辑-》编译-》链接-》运行
    多个目标文件（.o）进行统一链接
        和库文件链接==》可执行程序

5、链接分类：
    静态链接：链接静态库文件，产生可执行程序较大，运行不需要库支持
        将库文件中的代码直接拷贝到可执行程序中，运行时直接调用
    动态链接：链接动态库文件，产生可执行程序较小，运行需要库文件支持
        将库文件中的代码拷贝到可执行程序中，运行时调用库文件中的函数，库文件在运行时才加载到内存中，节省内存。
        gcc默认链接是动态链接

制作静态库的使用：
    1、开发.c和.h文件
    2、将.c文件编译成.o文件
        gcc -c xxx.c
    3、将.o文件打包成.a文件
        ar rcs lib库名.a *.o
    4、使用静态库文件
        -I头文件搜索路径
        -L库文件搜索路径
        -l库文件名
        -o 可执行程序名
        -g 调试信息
        -Wall 开启所有警告
        -O 优化选项
        -static 静态链接选项
        -On 优化等级
        -E预处理
        gcc main.c -L. -l库名 -o main static -I头文件搜索路径
制作动态库文件使用：
    1、开发.c和.h文件
    2、将.c文件编译成.o文件
        gcc -c *.c -fPIC
    3、将.o文件打包成.so文件
        gcc -shared -o lib库名.so *.o
    4、使用动态库文件
    gcc main.c -L. -l库名 -o main -I头文件搜索路径 -ldl 动态库加载库

第三方库使用步骤：
    1、安装库
        （1）源代码库
            将.c文件和.h文件拷贝工程目录下
        （2）二进制库
            方式1:安装
                Linux：cpt-get install lib库名
                Windows：IDE中有安装功能
                    下载安装包，解压后将lib库文件拷贝到工程目录下
            方式2：手动安装
                通过安装包实现


数据库编程：
    一、数据库概念
        1、数据库存储技术发展
            手工=》文件=》数据库
        2、DBMS：数据库管理系统
        3、sql和DBMS进行沟通语言
            库操作、表操作、数据操作
        4、c/s模式:客户端/服务器模式
            DBMS--是服务器
            开发客户端程序访问DBMS
            mysql-server mysal-client
        5、DBMS管理多个库
            
库操作：
   新建库：create database 库名
   删除库：drop database 库名
   查看库信息：show databases;
   选择库：use 库名


表操作：
    新建表--create table
    删除表--drop table表名
    修改表--alter table表名
    查看表信息--desc 表名
    查看数据库中数据表--show tables
    查看表内容--select * from 表名;
    复制表--create table 新表名 as select * from 旧表名;

    
数据类型：
    1、基本类型
        整型：tinyint、smallint、int、bigint
        浮点型：float、double、decimal
        日期时间类型：date、time、datetime、timestamp

        字符串类型：char、varchar、text、blob
    2、布尔型bool
        true、false
    3、文本类型
        text、longtext、tinytext
    4、二进制类型
        blob、longblob
约束：
    1、主键约束primary key
        唯一标识一条记录，不能重复
        一个表仅有一个约束

    2、唯一约束unique
        唯一标识一条记录，不能重复
        一个表可以有多个约束

    3、非空约束not null
        不能为空
        一个字段可以设置多个约束

    4、默认值default
        字段没有值时，使用默认值
        一个字段可以设置多个约束

    5、外键约束foreign key
        参照另一张表的主键值
        一个表可以有多个约束

    6、自增约束auto_increment
        修饰主键 且类型为整型
        用来修饰记录编号

修改表结构--alter table表名 add/drop/modify字段名 数据类型约束;

数据操作
    插入数据--insert into 表名(字段1,字段2) values(值1,值2)
    删除数据--delete from 表名 where 条件
    修改数据--update 表名 set 字段=值 where 条件
    查询数据--select 字段1,字段2 from 表名 where 条件


插入语句
    给所有字段插入值
        insert into 表名 values(值1,值2,值3,...)
    给指定字段插入值
        insert into 表名(字段名列表) values(值列表)

    修改语句
        update 表名 set 字段名=值 where 条件
    删除语句
        delete from 表名 where 条件
    查询语句


数据库编程:
    （1）连接数据库          
        MYSQL *mysql_init(MYSQL *connection);

        MYSQL  *mysql = NULL,*res = NULL;

        mysql = mysql_init(NULL);

        res = mysql_real_connect(mysql,"localhost",用户名,密码，数据库名,0,NULL,0);
        if(res == NULL)
        {
            printf("%s",mysql_error(mysql));     
        }


    （2）组织sql语句
            sprintf()

    （3）执行sql语句
            mysql_query()
            mysql_real_query()
                   
    （4）关闭连接
            mysql_close()



atui转整型
atuf转浮点型
atod转日期型
atot转时间型

查看多任务：
    ps -aux | grep id

多任务编程：
    进程称为task

    一、进程三种状态：
        运行态：进程占用cpu资源，正在执行
        就绪态：进程未占用cpu资源，等待cpu调度
        阻塞态：由于资源不足，不能运行，处于阻塞态

    二、进程编程
        1、获取进程id：
            pid_t getpid()获取当前进程id
            pid_t getppid()获取父进程id
        2、创建子进程：
            pid_t fork()
            父进程：返回子进程id
            子进程：返回0
            功能:将当前进程复制一份，子进程从父进程继承资源
             创建一个子进程，子进程从fork之后运行，父进程继续运行
             返回值：在父空间返回是子进程id，在子空间返回0
            C语言中唯一一个有两个返回值的函数
            利用返回值使父子进程执行不同的代码

            pid = fork();
            if(pid == 0)
            {
                //子进程代码
                exit(0)
            }else if (pid > 0)
            {
                //父进程代码
            }else
            {
                //出错处理
                printf("fork error");
            }
        3、孤儿进程和僵尸进程
            pid_t waitpid(pid_t pid,int *status,int options)
                阻塞式等待号为pid子进程结束，回收子进程资源
            
            pid_t wait(int *status)
                阻塞式等待任意子进程结束，回收子进程资源

            孤儿进程：
                父进程先于子进程结束，子进程成为孤儿进程，由系统进程回收统一管理
                
            僵尸进程：
                子进程执行结束，父进程没有调用wait（）回收资源，子进程称为僵尸进程，由系统进程回收统一管理

进程通信：
    1、进程是相互独立，每一个进程都有一套独立资源，每一个进程都认为自己独占计算机所有资源
    2、多进程编程中，进程之间需要相互之间传递数据，才能实现协调工作
    3、由操作系统提供通信机制：管道通信 消息队列 共享内存 信号

管道通信：
    一、概念
        1、管道本质是一个文件
        2、该文件有两个文件描述符，一个称为读端，另一个称为写端
        3、通过写端写入数据，如果管道写满则阻塞，等待读取
            通过读端读取数据，如果管道空则阻塞，等待写入
        4、写入管道的数据，读取之后不存在，写入管道数据数据形象理解为排队
            写入=》入队
            读取=》出队
                先进先出
        5、分类
            匿名管道（无名）：用于父子或者兄弟之间进程间通信
            有名管道：存在磁盘上文件，用于任意进程之间通信

二、编程
    1、创建匿名管道文件
        int pipe(int fd[2]);
        fd[0] 读端
        fd[1] 写端
        成功返回0，失败返回-1

        int fd[2] = {0};

        pipe(fd);
        fd[0] 读端
        fd[1] 写端

    2、读写管道
        read() write()
        成功返回实际读写字节数，失败返回-1
    3、关闭管道
        close()
        成功返回0，失败返回-1
    
消息队列：
    一、概念
        1、队列：具有先进先出特性的一种数据结构
            用在进程之间作为缓冲区，实现通信
        2、os提供一种用户多进程之间通信的队列
            队列可以同时为多个进程实现通信，不相互影响
            队列消息包：消息编号 + 消息内容
        3、操作队列：
            创建队列
            入队
            出队
            销毁队列
        4、消息队列中消息的结构体类型具有通用性

            struct msgbuf
            {
                long mtype; //消息类型
                char mtext[100]; //消息内容
            };

    二、编程
        1、创建消息队列
            ket_t ftok(const char *pathname,int proj_id)
                功能：创建消息队列的key
                pathname：文件路径
                proj_id：项目id
            成功返回key，失败返回-1

            int msgget(key_t key,int msgflg);
                功能：创建或者获取
                key：唯一标识符
                msgflg：标志位，0表示创建，非0表示获取
            成功返回消息队列id，失败返回-1

        2、消息队列操作
            入队：
                int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);
                    功能：发送消息
                    msqid：消息队列id
                    msgp：消息内容
                    msgsz：消息大小
                    msgflg：标志位，0表示阻塞，非0表示非阻塞
                成功返回0，失败返回-1

            出队：
                ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
                    功能：接收消息
                    msqid：消息队列id
                    msgp：消息内容
                    msgsz：消息大小
                    msgtyp：消息类型，0表示接收所有类型消息
                    msgflg：标志位，0表示阻塞，非0表示非阻塞
                成功返回实际接收字节数，失败返回-1
            
            销毁队列：
                int msgctl(int msqid,int cmd,struct msqid_ds *buf);
                    功能：控制消息队列
                    msqid：消息队列id
                    cmd：命令，IPC_RMID表示删除队列
                    buf：消息队列属性结构体
                成功返回0，失败返回-1

        3、消息队列属性结构体
            struct msqid_ds
            {
                struct ipc_perm msg_perm; //消息队列权限
                time_t msg_stime; //创建时间
                time_t msg_rtime; //最近一次接收时间
                time_t msg_ctime; //最近一次发送时间
                unsigned long msg_cbytes; //当前消息字节数
                unsigned long msg_qnum; //当前消息数量
                unsigned long msg_qbytes; //最大消息字节数
                pid_t msg_lspid; //最后发送进程id
                pid_t msg_lrpid; //最后接收进程id
            };

共享内存：
    一、概念
        1、创建一块多个进程都能访问空间，利用该空间实现进程间通信
        2、各个进程仅能访问自己内存区域，将共享内存映射到进程自己内存空间
            通过访问该区域等同于访问共享内存
        3、利用共享内存通信，系统不提供同步机制，需要程序员自己处理

    二、编程
        1、创建共享内存
            ftok()
        
            int shmget(key_t key,size_t size,int shmflg);
                功能：创建或者获取共享内存
                key：唯一标识符
                size：共享内存大小
                shmflg：标志位，0表示创建，非0表示获取
            成功返回共享内存id，失败返回-1

        2、映射共享内存
            void *shmat(int shmid,const void *shmaddr,int shmflg);
                功能：映射共享内存
                shmid：共享内存id
                shmaddr：映射地址，NULL表示自动选择
                shmflg：标志位，0表示读写，1表示只读
            成功返回共享内存地址，失败返回(void*)-1

        3、解除映射
            int shmdt(const void *shmaddr);
                功能：解除共享内存映射
                shmaddr：共享内存地址
            成功返回0，失败返回-1

        4、删除共享内存
            int shmctl(int shmid,int cmd,struct shmid_ds *buf);
                功能：控制共享内存
                shmid：共享内存id
                cmd：命令，IPC_RMID表示删除共享内存
                buf：共享内存属性结构体
            成功返回0，失败返回-1

        5、共享内存属性结构体
            struct shmid_ds
            {
                struct ipc_perm shm_perm; //共享内存权限
                size_t shm_segsz; //共享内存大小
                time_t shm_atime; //最近一次访问时间
                time_t shm_dtime; //最近一次修改时间
                time_t shm_ctime; //最近一次改变权限时间
                pid_t shm_cpid; //创建进程id
                pid_t shm_lpid; //最后一次访问进程id
                unsigned long shm_nattch; //当前附着进程数
            };

共享内存：
    模块化，封装函数

信号：
    一、概念
        1、进程之间通知机制，不传输大量数据。
                例如给进程发送停止信号 暂停信号 继续信号 终止信号
                kill-l 列出所有信号
                kill-n 发送信号给进程
                kill-p 进程组信号
                kill-s 信号集
                kill-l 列出所有信号
                kill-q 显示信号名称
                kill-t 发送信号到进程组

        2、信号用户不能自行定义，进程收到信号后动作或者



    二、编程
        1、修改进程收到信号后动作行为
            信号安装
            typedef void (*sighandler_t)(int);
            sighandler_t signal(int signum,sighandler_t handler);
                功能：安装信号处理函数
                signum：信号编号
                handler：进程收到该信号后执行函数                       
            成功返回信号处理函数地址，失败返回SIG_ERR

            进程收到signum信号时，自动调用handle函数，并将signum传递给该函数

        2、信号发送函数
            int kill(pid_t pid,int sig);
                功能：发送信号给进程
                pid：进程id
                sig：信号编号
            int raise(int sig);
                功能：发送信号给当前进程
            int killpg(int pgrp,int sig);
                功能：发送信号给进程组
            unsigned int alarm(unsigned int seconds);
                功能：设置定时器，进程在seconds秒后收到SIGALRM信号
            void pause(void);
                功能：进程暂停，等待信号到来
                进程收到信号后，自动恢复运行
            abort()函数：通常用于异常终止进程
                给 当前进程发送SIGABRT

        3、作用
            修改SIGINT信号，系统收到SIGINT信号之后，通过所有任务执行扫尾工作，结束运行


线程编程：
    一、概念
        1、线程是一个轻量级进程
        2、函数运行产生线程，主函数=》主线程（进程），子函数=》子线程
            所有线程共享进程资源，每一个线程仅有独立空间
        3、线程是os调度任务的基本单位，运行 就绪 阻塞是线程的状态
        进程是os是操作系统分配资源的基本单位，线程是cpu调度任务的基本单位
        4、多线程管理需要用户编程实习，实现同步和互斥进制

    二、编程
        获取线程id
            pthread_t pthread_self(void);
                功能：获取当前线程id

        1、创建线程函数
            int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
                功能：创建线程
                thread：线程id
                attr：线程属性，NULL表示默认属性
                start_routine：线程函数
                arg：线程函数参数
            成功返回0，失败返回错误码
            
            回收线程函数
            int pthread_join(pthread_t thread,void **retval);
                功能：等待线程结束
                thread：线程id
                retval：线程函数返回值
            成功返回0，失败返回错误码

            pthread_exit(void *retval);
                功能：退出线程
                retval：线程函数返回

        2、创建线程，给主线程传递数据
            （1）主线程将数据组织到结构体变量
            （2）创建线程时，将改变量地址传递给arg参数
            （3）线程函数通过arg参数获取数据，进行处理
            （4）线程函数中强转转换该地址值，获取数据

线程互斥:
    一、概念
        1、多个线程对共享资源的同时访问，可能会导致数据不一致
        2、线程在运行 就绪和阻塞三个状态之间转换，线程a使用共享资源时由运行态转化为其它态，
            a没有释放资源，线程b使用共享资源，导致错误发生。
        3、引入锁机制，使线程互斥的使用共享资源，线程a获取共享资源后，未释放之前，
            不管线程a处于什么状态，别的线程都不能使用，线程a释放之后其他可以使用
        4、在使用共享资源代码前后加锁（申请锁）和解锁（释放锁）。
    二、编程
        1、定义并初始化锁
            int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
                功能：初始化锁
                mutex：锁变量
                attr：锁属性，NULL表示默认属性
            成功返回0，失败返回错误码

        2、申请锁
            int pthread_mutex_lock(pthread_mutex_t *mutex);
                功能：申请锁
                mutex：锁变量
            成功返回0，失败返回错误码

        3、释放锁
            int pthread_mutex_unlock(pthread_mutex_t *mutex);
                功能：释放锁
                mutex：锁变量
            成功返回0，失败返回错误码

        4、销毁锁
            int pthread_mutex_destroy(pthread_mutex_t *mutex);
                功能：销毁锁
                mutex：锁变量
            成功返回0，失败返回错误码

线程同步：
    一、概念
        1、系统中存在多个线程，多个线程如何各司其职同步工作
        2、多个线程协同工作时，线程1运行产生数据，线程2在线程1基础之上运行，
            线程1和线程2同时执行
        3、操作系统负责管理多个线程，使多个线程运行 就绪 阻塞之间转换，
            操作系统依据内部多任务调度算法，和多线程业务逻辑没有关系
        4、用户需要根据业务逻辑引入控制机制，使os无序调度变成有序调度
        5、通过信号量方式实现控制线程同步工作
        6、线程同步和互斥模型--“生产者--消费者”
    二、编程
        1、定义并初始化信号量
            int sem_init(sem_t *sem,int pshared,unsigned int value);
                功能：初始化信号量
                sem：信号量变量
                pshared：进程间共享，0表示不共享，1表示共享
                value：信号量初始值
            成功返回0，失败返回错误码

        2、p操作
            int sem_wait(sem_t *sem);
                功能：等待信号量
                sem：信号量变量
            成功返回0，失败返回错误码

            信号量减1，如果大于等于0，程序继续运行，否则阻塞
                如果小于0，保持为0

        3、v操作
            int sem_post(sem_t *sem);
                功能：释放信号量
                sem：信号量变量
            成功返回0，失败返回错误码
            信号量加1，同时唤醒阻塞该信号量上的所有线程

        4、释放信号量
            int sem_destroy(sem_t *sem);
                功能：释放信号量
                sem：信号量变量
            成功返回0，失败返回错误码

        5、信号量的使用
            生产者线程：
                申请信号量
                生产数据
                释放信号量
            消费者线程：
                申请信号量
                消费数据
                释放信号量

        6、信号量作用
            （1）用作互斥锁，控制互斥
                用一个信号且初始化为1
            （2）控制线程之间同步
                用两个信号量（读写信号量）（交叉信号量）

队列
    1、概念
        是一种“先进先出”的数据结构用在两个线程通信之间，起缓冲区的作用
    2、实现
        基于链表实现
        创建队列
        入队
        出队
        判断队列是否为空
        获取队列大小

"生产者--消费者"模型
    1、生产者--生产产品，消费者--消费产品
        控制：先生产，后消费
            消费时，没有产品，等待生产
            生产时，缓冲区满，等待消费
    2、模型：两类线程和一个缓冲区
        生产者线程
        消费者线程
        缓冲区
    3、模型中同步和互斥关系
        先生产，后消费--同步关系
        生产和消费不能同时进行--互斥关系
        生产者之间--互斥关系
        消费者之间--互斥关系

线程池
    一、概念
        1、处理请求时，针对每一个请求包，创建一个线程，线程服务之后，线程终止
            处理多个请求包时，需要频繁创建和销毁线程，给系统带来巨大开销，效率低下
        2、预先创建一批线程，处于休眠模式，接收一个用户请求包时，分配一个线程为其服务
            服务完之后，线程并不终止，继续等待新的请求包，这批线程形象称为线程池
    二、实现一个简单线程池
         1、描述线程池结构体
              struct threadPool
             {
                struct queue *que;
                sem_t semr;
                sem_t semw;
                pthread_mutex_t mutex;

                int queue_max_length;
                pthread_t *tids;
                
             };

        2、创建线程池的函数
               struct threadPool *create_threadPool(int threads,int queue_max_length)
               {
                       struct threadPool *pool = malloc(sizeof(struct threadPool));
                       memset();
                       pool->tids = malloc(sizeof(pthread_t) * threads);
                       
                       初始化队列
                       初始化锁
                       初始化两个信号量

                       for(int i = 0;i < threads;i++)
                       {
                            ret = pthread_create(&(pool->tids[i]),NULL,fun2,pool);
                            if(ret != 0)
                            {
                               perror("pthread_create");
                               exit(-1);
                            }
                       }

                      return pool;
               }

        3、线程池销毁函数           
            void destroy_pool(struct threadPool *pool)
                {
                    释放信号量
                    释放锁
                    释放队列
                    free(pool->tids);
                    free(pool);
               }

网络编程
    1、概念 2、udp协议 3、tcp协议 4、tcp服务器模型

    一、概念
        1、通信协议：为了保证通信能够及时准确的进行，而制定的一套规则
        2、OSI指定网络互联参考模型iso7层参考模型
                tcp/ip4层网络协议：
                    应用层：http、ftp、smtp、dns
                    传输层：tcp、udp
                    网络层：ip、arp、rarp、icmp
                    网络接口层：ethernet、wifi、ppp、slip、loopback
        3、ip地址：网络中唯一表示一台计算机的地址，由32位二进制数表示，
                    用4段十进制字符表示“192.168.1.1”
            端口号：用于计算机中唯一表示通信程序
                16位整数
        4、网络编程模型
            c/s模型==》客户端/服务器模型
            b/s模型==》浏览器/服务器模型
    二、udp协议
        1、编程步骤
            服务器：
                创建套接字=》绑定=》接收=》发送=》关闭
            客户端：
                创建套接字=》发送=》接收=》关闭套接字
        创建套接字
            int socket(int domain,int type,int protocol);
                功能：创建套接字
                domain：协议族，AF_INET表示ipv4协议族
                type：套接字类型，SOCK_DGRAM表示udp
                protocol：协议，0表示默认协议
            成功返回套接字描述符，失败返回-1

        2、绑定
             int bind(int sockfd, struct sockaddr *addr, int addrlen);
                功能：绑定套接字
                sockfd：套接字描述符
                addr：地址结构体
                addrlen：地址结构体长度
            成功返回0，失败返回-1

        3、接收
           int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
                功能：接收数据
                sockfd：套接字描述符
                buf：接收缓冲区
                len：接收缓冲区大小
                flags：标志位，0表示阻塞，非0表示非阻塞
                from：源地址结构体
                fromlen：源地址结构体长度
            成功返回实际接收字节数，失败返回-1

        4、发送
            int sendto(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *to, int tolen);
                功能：发送数据
                sockfd：套接字描述符
                buf：发送缓冲区
                len：发送缓冲区大小
                flags：标志位，0表示阻塞，非0表示非阻塞
                to：目的地址结构体
                tolen：目的地址结构体长度
            成功返回实际发送字节数，失败返回-1


        5、关闭套接字
            int close(int sockfd);
                功能：关闭套接字
                sockfd：套接字描述符
            成功返回0，失败返回-1

通信协议包设计：
    1、通信协议包总体格式
        功能码+数据区
    2、协议包设计方案
        （1）自定义字符串
        （2）二进制报文格式
        （3）json格式
        （4）probuf格式（c+语言）

    3、自定义字符串协议包格式
        （1）协议包用两个特殊字符作为开头或结尾
        （2）用一个特殊字符作为间隔符，将功能码和数据区分开
        （3）功能码用一个字符表示，数据区用多个字符表示
        （4）数据区的长度用一个字符表示，数据区的长度不超过255个字符

    4、二进制报文格式
       （1）采用组合的放式设计协议包格式
       （2）数据包：报文头+报文体
                报文头对应一种结构体
                报文体对应多种结构体
        （3）采用组合方式形成通信中需要各种协议包
                实现
                    组包：memcpy（）
                    解包：利用指针强转和指针移动

服务器设计：服务器如何高效为多个客户端及时提供服务
    （1）udp服务器自动接受数据包存入缓冲区
    （2）recvfron（）从接受缓冲区获取数据包存入用户空间
    （3）recvfrom（）执行速度慢，导致接收缓冲区数据慢溢出，导致丢包
    （4）通过设置使缓冲区变大，防止丢包，设置方式受限，作用较小
    （5）在服务器上采用多线程，数据包的接受和处理分开进行
            方案一：创建一个线程，专门负责接受数据包，存入用户缓冲区（队列）
                    创建一个业务处理线程，从缓冲区（队列）中获取数据包处理

                问题：通过recvfrom接受到数据包后，需要重新封装存入队列
                        存入队列中的数据（原来数据包，客户端ip和端口号）
            方案二：创建一个线程，专门负责接受数据包，存入用户缓冲区（队列）
                    创建一个业务处理线程，从缓冲区（队列）中获取数据包处理
                        （使用线程池技术）

tcp协议：
    1、编程
        服务器：
            创建套接字=》绑定=》监听=》接收连接请求=》收发数据包=》关闭通信套接字=》关闭监听套接字
        客户端：
            创建套接字=》连接请求=》收发数据包=》关闭连接
        创建套接字
            int socket(int domain,int type,int protocol);
                功能：创建套接字
                domain：协议族，AF_INET表示ipv4协议族
                type：套接字类型，SOCK_STREAM表示tcp
                protocol：协议，0表示默认协议
                返回值：套接字描述符，失败返回-1

        绑定
            int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
                功能：客户端连接服务器
                sockfd：套接字描述符
                addr：服务器地址结构体
                addrlen：服务器地址结构体长度
                返回值：成功返回0，失败返回-1
        发送
            ssize_t send(int sockfd, const void *buf, size_t len, int flags)
                sockfd：套接字描述符
                buf：发送缓冲区
                len：发送缓冲区大小
                flags：标志位，0表示阻塞，非0表示非阻塞
                返回值：实际发送字节数，失败返回-1
        接受
            ssize_t recv(int sockfd, void *buf, size_t len, int flags)
                sockfd：套接字描述符
                buf：接收缓冲区
                len：接收缓冲区大小
                flags：标志位，0表示阻塞，非0表示非阻塞
                返回值：实际接收字节数，失败返回-1
        监听
            int listen(int sockfd, int backlog);
                功能：监听套接字
                sockfd：套接字描述符
                backlog：最大连接数
                返回值：成功返回0，失败返回-1
        接收连接请求
            int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
                功能：接收连接请求
                sockfd：套接字描述符
                addr：客户端地址结构体
                addrlen：客户端地址结构体长度
                返回值：成功返回新的套接字描述符，失败返回-1
           
tcp服务器设计
    1、ppc：（process per connection）
        针对每一个连接产生一个子进程为其服务
            父进程阻塞式等待新的连接请求，有连接请求到来时，创建一个子进程为其服务
                将通信套接字传递给子进程
           父进程继续等待新的连接请求
    2、tcp(thread per connection)
        针对每一个连接产生一个线程为其服务
            主线程阻塞式等待新的连接请求，有连接请求到来时，创建一个线程为其服务
                将通信套接字传递给线程
            主线程继续等待新的连接请求

json格式协议包设计：
    一、概念
        1、是一种开放式标准的文件格式和数据交换格式
        2、是一种轻量级的数据交换格式，易于人阅读和编写，同时也易于机器解析和生成
        3、JSON独立于语言设计
            采用完全独立于编程语言的文本格式
        4、其简洁和清晰的层次结构有效的提升了网络传输效率，使其成为理想的数据交换语言
    二、json组织数据方式
        1、用{}括起来字符串，采用“键-值”对的方式组织数据
        2、建类型是字符，值的类型：字符串 整型 实型
        3、值的类型是符合类型：
                json类型
                数组类型
                对象类型
                布尔类型
                null类型
                数值类型
        4、键是手段，值是目的
    三、json格式数据组织和解析
        组织：将c格式数据（结构体）转换为json格式数据
        解析：json格式数据中获取值，转为对应的格式
        
    四、cjson使用
    组织json包
        1、创建cjson指针对象
            CJSON *json = cJSON_CreateObject();
        2、添加键值对
            cJSON_AddItemToObject();
            string类型：cJSON_AddStringToObject()
            int类型：cJSON_AddNumberToObject()
            folat类型：cJSON_AddNumberToObject()
            double类型：cJSON_AddNumberToObject()
            bool类型：cJSON_AddBoolToObject()
            null类型：cJSON_AddNullToObject()
            数组类型：cJSON_AddItemToArray()
            对象类型：cJSON_AddItemToObject()
        3、组织数据
            char *json_str = cJSON_Print(json);
        4、释放空间
            free(json_str);
        5、删除对象
            cJSON_Delete(json);
    解析json包
        1、创建cjson指针对象
            CJSON *json = cJSON_Parse();
        2、获取对象
            CJSON_Parse()
        3、获取键值
            cJSON_GetObjectItem()

一、select模型
    1、tcp服务器设计采用多线程，为每一个连接分配一个线程为其服务，n个链接占用n个线程
            n个链接长时间不发送请求包且不断开，导致处理线程大部分处于休眠模式，新的连接请求无法接收
    2、提高系统效率，避免资源浪费，采用一个线程同步处理多个连接请求包，
            系统引入select()系统调用，从多个连接中选择出活跃连接让线程处理
    3、select监控的所有套接字必须设为“非阻塞”套接字

二、编程
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
        功能：监控文件描述符集合，等待IO事件
        nfds：文件描述符集合的最大值+1
        readfds：监控读事件的文件描述符集合 
        writefds：监控写事件的文件描述符集合
        exceptfds：监控异常事件的文件描述符集合
        timeout：超时时间，NULL表示无限等待,最长监控时间 
        返回值：返回活跃的文件描述符集合，0表示超时，-1表示出错
    
    删除fd
        void FD_CLR(int fd, fd_set *set)
            功能：从文件描述符集合中删除fd
            fd：文件描述符
            set：文件描述符集合
            
    监测fd是否活跃
        void FD_SET(int fd, fd_set *set)
            功能：将fd加入文件描述符集合
            fd：文件描述符
            set：文件描述符集合

    将fd加入监控集中
        int FD_ISSET(int fd, fd_set *set)
            功能：判断fd是否在文件描述符集合中
            fd：文件描述符
            set：文件描述符集合
            返回值：1表示在集合中，0表示不在集合中

    清空监控集
        void FD_ZERO(fd_set *set)
            功能：将文件描述符集合清空
            set：文件描述符集合

select模型优缺点
    优点：
        1、简单，易于实现
        2、不需要为每个连接创建线程，减少资源消耗
        3、支持超时时间设置，可以实现非阻塞IO
        4、支持同时监控多个文件描述符集合，可以实现同时监听多个套接字
    缺点：
        1、效率低，每次调用select都需要遍历所有文件描述符，效率较低
        2、监控的文件描述符集合最大值有限制，不能超过FD_SETSIZE

epool模型
    一、概念
        和select完全一致
    二、编程
        1、创建epoll句柄
            int epoll_create(int size);
                功能：创建epoll句柄
                size：最大监听数目，默认为1024，可以修改
                返回值：成功返回epoll句柄，失败返回-1
            int epoll_create1(int flags);
                功能：创建epoll句柄
                flags：附加选项，表示创建的epoll句柄类型
                返回值：成功返回epoll句柄，失败返回-1
        2、监控事件
            int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
                功能：对fd进行操作
                epfd：epoll句柄
                op：操作类型，EPOLL_CTL_ADD添加，EPOLL_CTL_MOD修改，EPOLL_CTL_DEL删除
                fd：文件描述符
                event：事件类型，表示监听的事件类型，比如EPOLLIN表示可读，EPOLLOUT表示可写，EPOLLET表示边缘触发
                返回值：成功返回0，失败返回-1
        3、等待事件
            int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
                功能：等待事件发生
                epfd：epoll句柄
                events：事件集合，用于存放发生的事件
                maxevents：事件集合的最大值
                timeout：超时时间，-1表示无限等待，0表示立即返回，其他表示等待时间
                返回值：返回发生事件的个数，0表示超时，-1表示出错

非阻塞编程
    1、用户系统调用发现不能满足客户请求时，在内核中默认阻塞当前进程，直到满足要求时，解除阻塞，返回应用程序继续执行
    2、从网络套接字接收时，即调用recv会导致阻塞
    发送数据时(即调用send)，如果发送缓冲区满了，则阻塞
    读写管道文件时，条件不满足会阻塞
    操作设备文件时，读取设备文件，数据没有准备好，则阻塞
    等待某一事件发生时，调用select或epoll，如果条件不满足，则阻塞
    等待某一信号时，调用sigsuspend，如果信号没有到来，则阻塞
    等待某一条件变量时，调用pthread_cond_wait，如果条件不满足，则阻塞
    等待某一互斥锁时，调用pthread_mutex_lock，如果互斥锁已经被其他线程占用，则阻塞
    等待某一条件变量时，调用pthread_cond_wait，如果条件不满足，则阻塞
    3、编程根据需要可以将套接字或者文件描述设置为非阻塞：
            读写套接字或者读写文件不管条件是否满足都不阻塞并立即返回
        在select和epoll模型中需要将所有套接字设置为非阻塞，保证服务器安全运行
    4、将套接字或者文件描述设置为非阻塞
    (1)open打开文件时，O_NONBLOCK标志位，表示打开文件为非阻塞模式
    (2)通过fcntl设置文件描述符为非阻塞模式
        int setnonblocking(int fd) 
        {
            int old_option = fcntl(fd, F_GETFL);
            int new_option = old_option | O_NONBLOCK;
            fcntl(fd, F_SETFL, new_option);
            return old_option;
        } 


非阻塞编程优缺点
    优点：
        1、提高系统并发性，提高系统吞吐量
        2、减少资源消耗，提高系统的实时性
        3、减少系统调用，提高系统的效率
    缺点：
        1、编程复杂度增加，需要处理更多的错误
        2、调试困难，需要跟踪系统调用的返回值
    
tcp通信中粘包和拆包
    1、在tcp通信中，系统为每一个通信套接字分配两个独立缓冲区，发送缓冲区和接收缓冲区
    2、sned()将数据由用户内存空间发送到缓冲区，recv()从接收缓冲区读取数据到用户内存
    3、发送缓冲区到对方接收缓冲区之间数据传输，数据包多大，何时发送和接收用户无法干预
    4、send执行多次，底层发送一次可能send执行一次，底层发送一次
    5、发送和接受异步执行，，导致发送多次，一直没有接收
                    接收时，对方没有发送或者对方发送了很多次
    6、客户端给服务器发送多个大小不同数据包，服务器接收数据包时，
            一次接收多个数据包:
                    多个数据包同时接收称为粘包
                    多个数据包和部分数据包称为拆包
    


udp通信:
    套接字没有发送缓冲区，只有接收缓冲区
    接收缓冲区数据满时，发送来的数据包会丢失
    recvfrom函数从接收缓冲区获取数据包时，自动处理粘包问题，用户不用考虑


